import { defineComponent, computed, watch, ref, onMounted, unref, onUnmounted } from 'vue'

const isNumber = (val: any): boolean => {
  return !isNaN(parseFloat(val))
}

export default defineComponent({
  name: 'CountTo',

  props: {
    startVal: {
      type: Number,
      required: false,
      default: 0
    },
    endVal: {
      type: Number,
      required: false,
      default: 0
    },
    duration: {
      type: Number,
      required: false,
      default: 3000
    },
    delay: {
      type: Number,
      required: false,
      default: 0
    },
    autoplay: {
      type: Boolean,
      required: false,
      default: true
    },
    decimals: {
      type: Number,
      required: false,
      default: 0,
      validator: (value: number) => {
        return value >= 0
      }
    },
    decimal: {
      type: String,
      required: false,
      default: '.'
    },
    separator: {
      type: String,
      required: false,
      default: ','
    },
    prefix: {
      type: String,
      required: false,
      default: ''
    },
    suffix: {
      type: String,
      required: false,
      default: ''
    },
    useEasing: {
      type: Boolean,
      required: false,
      default: true
    },
    easingFn: {
      type: Function,
      default: (t: number, b: number, c: number, d: number) => {
        return c * (-Math.pow(2, -10 * t / d) + 1) * 1024 / 1023 + b
      }
    }
  },

  emits: ['callback', 'mountedCallback'],

  setup(props, { emit }) {
    const countDown = computed(() => props.startVal > props.endVal)

    const formatNumber = (num: number): string => {
      let numStr = num.toFixed(props.decimals)
      numStr += ''
      const x = numStr.split('.')
      let x1 = x[0]
      const x2 = x.length > 1 ? props.decimal + x[1] : ''
      const rgx = /(\d+)(\d{3})/
      if (props.separator && !isNumber(props.separator)) {
        while (rgx.test(x1)) {
          x1 = x1.replace(rgx, '$1' + props.separator + '$2')
        }
      }
      return props.prefix + x1 + x2 + props.suffix
    }

    let localStartVal: number
    let printVal: number
    let localDuration: number
    let startTime: Nullable<number> = null
    let reF: Nullable<number> = null

    const displayValue = ref(formatNumber(props.startVal))

    const count = (time: number) => {
      if(!startTime) startTime = time
      const progress = time - startTime
      if (props.useEasing) {
        if (unref(countDown)) {
          printVal = localStartVal - props.easingFn(progress, 0, localStartVal - props.endVal, localDuration)
        } else {
          printVal = props.easingFn(progress, localStartVal, props.endVal - localStartVal, localDuration)
        }
      } else {
        if (unref(countDown)) {
          printVal = localStartVal - ((localStartVal - props.endVal) * (progress / localDuration))
        } else {
          printVal = localStartVal + (props.endVal - localStartVal) * (progress / localDuration)
        }
      }
      if (unref(countDown)) {
        printVal = printVal < props.endVal ? props.endVal : printVal
      } else {
        printVal = printVal > props.endVal ? props.endVal : printVal
      }
      displayValue.value = formatNumber(printVal)
      if (progress < localDuration) {
        reF = requestAnimationFrame(count)
      } else {
        emit('callback')
      }
    }

    const awaitTime = () => {
      return new Promise(resolve => {
        setTimeout(resolve, props.delay)
      })
    }

    const start = async () => {
      if(props.delay) await awaitTime()
      localStartVal = props.startVal
      startTime = null
      localDuration = props.duration
      requestAnimationFrame(count)
    }

    watch([() => props.startVal, () => props.endVal], () => {
      if(props.autoplay) start()
    })

    onMounted(() => {
      if(props.autoplay) start()
      emit('mountedCallback')
    })

    onUnmounted(() => {
      reF && cancelAnimationFrame(reF)
    })

    return () => (
      <>
        {
          unref(displayValue)
        }
      </>
    )
  }
})
